function [fighnld] = imagebrowse(IMG, doscale, xrange)
% imagebrowse(IMG [, doscale [, xrange]])
%
% Shows the N dimensional image IMG, in a browsable view of slices. Click
% on a point to focus there, or use the sliders to navigate through the
% image. The standard zoom buttons can also be used to view something in
% more detail.
% The optional argument doscale (default = 1) specifies the kind of
% automatic scaling that is performed:
% 0: no scaling, uint8 inputs are treated as is, and other inputs are
%    assumed to have a range of [0..1].
% 1: (default) scale so the maximum of the image is white and the minimum
%    is black.
% 2: scale each slice so that it contains black and white.
% When doscale is a 2 element vector it is assumed to contain the custom
% black and white points (respectively).
% Complex data can be visualized as well. Use the radiobuttons to select
% between views of the magnitude, angle, real or imag part of the data.
% The doscale matrix can provide a limit for each of these by specifiing
% a 4 row, 2 column matrix with the black and white points for each display
% mode. 
% A log checkbox is present to easily allow a view of the log of the data.
% The range of the axis can be specified by xrange. Specify the location of
% the first and last element in IMG in each of the respective dimensions.
% So xrange = [location_IMG(   1 ,:,..,:) location_IMG(:,   1 ,..) .. ;
%              location_IMG( end ,:,..,:) location_IMG(:, end ,..) .. ];
% Currently the axis are automatically labeled 'Dimension i' for the i-th 
% index in IMG. Custom labels are currently not supported, but a future 
% version might (let me know if you need it).
%
% Created by Dirk Poot, 6-12-2007, University of Antwerp

% 27-1-2009: Added complex data support 
% 25-8-2009: Added center and range sliders. 
% 15-12-2011: Added scoll weel support. Unfortunately I can't enable it yet, as 
%             selecting 'zoom' overwrites the 'WindowScrollWheelFcn' option of the figure.
%             And when 'zoom' is activated, it cannot be set at all.

sIMG = size(IMG);
ndims = length(sIMG);

if nargin<2 || isempty(doscale)
    doscale =true;
end;

% if ndims == 2
%     imageTile(IMG,doscale);
%     return;
% end;
if issparse(IMG)
    if numel(IMG)-nnz(IMG)>1e7
        warning('imagebrowse:SparseIMG',['Many zero elements in sparse image (' num2str(numel(IMG)-nnz(IMG)) '). Therefore, the image is not converted to full and thus cannot be shown. Figure is not modified.']);
        return;
    end;
    IMG = full(IMG); % only case in which we 'copy' the input.
end;

if isreal(doscale) && (length(doscale)==2 || isequal(size(doscale),[4 2]))
    if size(doscale,1)==4
        mn = doscale(:,1);
        mx = doscale(:,2);
    else
        mn = doscale(1);
        mx = doscale(2);
    end;
    doscale = true;
elseif doscale
    if exist('advancedminmax','file')
        [mn,mx] = advancedminmax(IMG(:));
    else
        disp('Try to get advancedminmax (from D.H.J. Poot)!');
        mn = min(IMG(:));
        mx = max(IMG(:));
    end;
elseif ~isa(IMG,'uint8')
    mn = 0;
    mx = 1;
else
    mn = 0;
    mx = 255;
end;

tileDims = 1:ndims;
rngIMG = [ones(1,ndims);sIMG];
colordim = find(sIMG==3,1,'last');
if ~isempty(colordim)
    tileDims(colordim) =[]; %remove color dim from tiling.
    ndims = ndims-1;
end;
if nargin>=3 && ~isempty(xrange)
    if ~isequal(size(xrange),[2 ndims])
        error('image range should be a (2 x ndims) matrix specifying coordinate of [first;last] element.');
    end;
    rngIMG = xrange;
end;
squeezedims = sIMG(tileDims)==1;
if any(squeezedims)
    tileDims(squeezedims)=[]; % remove size =1 dimensions.
    ndims = ndims - nnz(squeezedims);
end;

% Test if we can preserve the current imagebrowse figure and just update
% the image (preserves settings such as log, position, range, center)
if isvalidImageBrowsefigure(get(0,'currentfigure'))
    fighnld = get(0,'currentfigure');
    ud = get(fighnld,'UserData');
    if isequal(ud.sIMG,size(IMG)) ... % size, range should be equal
         && isequal(ud.rngIMG, rngIMG) ... % limits of axis should be equal
         && isequal(ud.minmax, [double(mn) double(mx)])
        ud.IMG = IMG;
        set(fighnld,'UserData',ud);
        redrawsubplots(fighnld, 1:numel(ud.subfigs));
        return;
    end;
end;
clf
fighnld = gcf;
% rngIMG = [ones(1,ndims);sIMG];
info.fighnld = fighnld;
info.IMG = IMG;
info.sIMG = sIMG;
info.rngIMG = rngIMG;
info.sliders = zeros(ndims,1);
info.texts = zeros(ndims,1);
info.tileDims =  tileDims;
info.drawmethod = 0; 
usedspace = 0;
for k=1:ndims
    info.sliders(k) = uicontrol('Style','slider','Min',1,'Max',sIMG(tileDims(k)),...
                                'value'      ,  ceil(sIMG(tileDims(k))/2),       ...
                                'SliderStep' , [1/(sIMG(tileDims(k))-1) round(sqrt(sIMG(tileDims(k))))/(sIMG(tileDims(k))-1)],...
                                'callback',{@sliderupdate,[info.fighnld , k]},...
                                'units','pixels',...
                                'Position',[5 (ndims-k)*50+10 100 20]);
%     if all(rngIMG(:,tileDims(k))==[1;sIMG(tileDims(k))])
%         posstr = 'position %d';
%     else
        msd = floor(log10(max(abs(rngIMG(:,tileDims(k))))));
        lsd = floor(log10(diff(rngIMG(:,tileDims(k)))/(sIMG(tileDims(k))-1)));
        if lsd<0
            posstr = ['[%' num2str(msd-lsd+2) '.' num2str(-lsd) 'f]'];
        elseif lsd<=2
            posstr = ['[%' num2str(msd+1) '.0f]'];
        end;
%     end;
    info.texts(k) = uicontrol('Style','text',...
                                'units','pixels', ...
                                'Position',[5 (ndims-k)*50+30 100 18], ...
                                'UserData',['Dim ' num2str(info.tileDims(k)) ', ' posstr]);
end;
usedspace = usedspace+ ndims*50;

if  doscale~=2
    usedspace = usedspace+10;

    info.sliders(ndims+2) = uicontrol('Style','slider','Min',-2,'Max',10,...
                                'value'      ,  0,       ...
                                'SliderStep' , [1/(120-1) 1/(12-1)],...
                                'callback',{@sliderupdate,[info.fighnld , k]},...
                                'units','pixels',...
                                'Position',[5 usedspace+(1)*50+10 100 20]);
    info.texts(ndims+2) = uicontrol('Style','text',...
                                'units','pixels', ...
                                'Position',[5 usedspace+(1)*50+30 100 18], ...
                                'UserData','Range: %1.4g');
    info.sliders(ndims+1) = uicontrol('Style','slider','Min',mn(1),'Max',mx(1),...
                                'value'      ,  (mx(1)+mn(1))/2,       ...
                                'SliderStep' , [1/999 1/30],...
                                'callback',{@sliderupdate,[info.fighnld , k]},...
                                'units','pixels',...
                                'Position',[5 usedspace+(0)*50+10 100 20]);
    info.texts(ndims+1) = uicontrol('Style','text',...
                                'units','pixels', ...
                                'Position',[5 usedspace+(0)*50+30 100 18], ...
                                'UserData','Center:  %1.4g');
    usedspace = usedspace+ 2*50;
end;


butsz = 20;
if ~isreal(IMG)
    % Create four radio buttons in the button group.
    h = uibuttongroup('visible','off','units','pixels','Position',[5 usedspace+10 100 butsz*4+4]);
    usedspace = usedspace+ butsz*4+10+4; 
    u0 = uicontrol('Style','Radio','String','Magnitude',...
        'pos',[2 3*butsz+2 96 butsz],'parent',h,'Tag','M');
%    u1 = ...
         uicontrol('Style','Radio','String','Phase',...
        'pos',[2 2*butsz+2 96 butsz],'parent',h,'Tag','P');
%     u2 = ...
         uicontrol('Style','Radio','String','Real',...
        'pos',[2 1*butsz+2 96 butsz],'parent',h,'Tag','R');
%     u3 = ...
         uicontrol('Style','Radio','String','Imag',...
        'pos',[2 0*butsz+2 96 butsz],'parent',h,'Tag','I');
    % Initialize some button group properties. 
    set(h,'SelectionChangeFcn',{@selcbk,info.fighnld});
    set(h,'SelectedObject',u0);  % No selection
    set(h,'Visible','on');
    info.drawmethod = 'M'; 
end;
dolog = 0;
checkloghndl = uicontrol('Style','checkbox','String','Log',...
        'pos',[7 usedspace+10 100 butsz],'Callback', {@togglelog,info.fighnld});
usedspace = usedspace+butsz+10;
buttonhndl = uicontrol('Style','pushbutton','String','100%',...
        'pos',[7 usedspace+10 50 butsz],'Callback', {@setzoom,info.fighnld});
usedspace = usedspace+butsz+10;

% if any(sIMG==3)
%     info.colorchk = uicontrol('Style','checkbox',...
%                                 'units','pixels', ...
%                                 'Position',[5 (ndims-k)*50+30 100 18]);
% end;
info.panel = uipanel('Position',[.2 0 .8 1]); % position is overwritten by call to resizefig
info.subfigs = zeros(ndims*(ndims-1)/2,1);                 
info.imgs = zeros(size(info.subfigs,1),1);                 
info.subfigdims = zeros(size(info.subfigs,1),2+numel(colordim));
info.drawprop = [doscale dolog];
info.minmax = [double(mn) double(mx)];
nvertsubfig = round(sqrt(numel(info.subfigs)-.1));
nhorisubfig = ceil(numel(info.subfigs)/nvertsubfig);
m=0;
rngAx = rngIMG + [-.5;.5]*(diff(rngIMG)./(sIMG-1));
for k=1:ndims-1
    for l=k+1:ndims
        hpos = mod(m,nhorisubfig);
        vpos = floor(m/nhorisubfig);
        m=m+1;
        info.subfigs(m) = axes('OuterPosition',[hpos/nhorisubfig 1-(vpos+1)/nvertsubfig 1/nhorisubfig 1/nvertsubfig],'parent',info.panel);
        set(info.subfigs(m),'xlim',rngAx(:,tileDims(l))','ylim',rngAx(:,tileDims(k)'));
        zoom reset;
        info.imgs(m) = image(rngIMG(:,tileDims(l)),rngIMG(:,tileDims(k)), zeros(sIMG(tileDims(k)),sIMG(tileDims(l)),'uint8'));
%         info.imgs(m) = image(zeros(sIMG(tileDims(k)),sIMG(tileDims(l)),'uint8'));
        colormap(gray(256));
        set(info.imgs(m),'ButtonDownFcn',{@imageclick,[gcf m]});
        xlabel(info.subfigs(m),['Dimension ' num2str(tileDims(l))]);
        ylabel(info.subfigs(m),['Dimension ' num2str(tileDims(k))]);
%         useAsColDim = [sIMG==3 1];
%         useAsColDim([k l]) =0;
%         colordim = find(useAsColDim,1,'first');
        info.subfigdims(m,:) = [tileDims(k) tileDims(l) colordim];
    end;
end;
info.activeslider = info.sliders(1);

set(info.fighnld,'ResizeFcn',{@resizefig,info.fighnld});
set(info.fighnld,'userdata',info);
resizefig(info.fighnld,[],info.fighnld);
sliderupdate(info.fighnld,[],[info.fighnld 0]);
set(info.fighnld,'Toolbar','figure');
set(info.fighnld,'WindowButtonMotionFcn',{@sliderupdate,info.fighnld})

    % unfortunately, the zoom, pan, ... modes do prevent setting WindowScrollWheelFcn
    % and return an warning that is not interesting for someone calling imagebrowse. 
    % Therefore, turn that warning off. When applicable, update state that gets restored when the zoom, pan ... mode exits.
    % preserve old warning state.
    [oldwarn, oldmsgid] = lastwarn('','');
    ws = warning('off','MATLAB:modes:mode:InvalidPropertySet');
    set(info.fighnld,'WindowScrollWheelFcn',{@scrollweelupdate,info.fighnld});
    warning(ws);
    [tst,tstid] = lastwarn(oldwarn, oldmsgid);
    if isequal(tstid, 'MATLAB:modes:mode:InvalidPropertySet')
        % apparently, some ui mode was selected.
        % set WindowScrollWheelFcn in state that gets restored when ui-mode ends:
        hManager = uigetmodemanager(info.fighnld);
        currMode = get(hManager,'CurrentMode');
        currMode.FigureState.WindowScrollWheelFcn = {@scrollweelupdate,info.fighnld};
%         warning('IMAGEBROWSE:cantSetWindowScrollWheelFcn', 'Please deselect all ui modes (zoom, pan, ...) when starting imagebrowse, since now scrolling through the data is disabled.');
    end;

% set(info.fighnld,'BusyAction','cancel');

function sliderupdate(src,event,infosrc)%#ok: event
fighndl = infosrc(1);
info = get(fighndl,'userdata');
sliderpos = zeros(size(info.sliders));
for k=1:numel(sliderpos);
    if ~ishandle(info.sliders(k))
        stopimagebrowse(fighndl); % happens when clf is used.
        return;
    end;
    sliderpos(k) = get(info.sliders(k),'value');
    
end;
if isequal(get(src,'Type'),'uicontrol') && isequal(get(src,'Style'),'slider')
    if info.activeslider~=src
        info.activeslider=src;
        set(fighndl,'userdata',info);
    end;
end;
sliderpos(1:numel(info.tileDims)) = round(sliderpos(1:numel(info.tileDims)));
oldsliderpos = get(info.sliders(1),'userdata');
if isequal(oldsliderpos,sliderpos)
    return;
end;
set(info.sliders(1),'userdata',sliderpos);
sliderchanged = false(1,numel(sliderpos));
if numel(sliderpos)==numel(oldsliderpos)
    sliderchanged(sliderpos~=oldsliderpos) = true;
else
    sliderchanged(1:end+1) = true;
end;
% baseindx = repmat({1},numel(info.sIMG),1);
for k=1:numel(sliderpos);
%     if sliderchanged(k)
        if k==numel(info.tileDims)+2
            sliderval = getcontrastval(info);
        else
            sliderval = sliderpos(k);
        end;
        if k<=numel(info.tileDims)
            rng = info.rngIMG(:,info.tileDims(k));
            sliderval = (sliderval-1)/(info.sIMG(info.tileDims(k))-1) * (rng(2)-rng(1))+rng(1);
        end;
        set(info.texts(k), 'String' ,  sprintf(get(info.texts(k),'UserData'),sliderval)); %['Dim ' num2str(info.tileDims(k)) ', position ' num2str(sliderpos(k))]);
%     end;
%     baseindx(info.tileDims(k)) = {sliderpos(k)};
end;
% info.baseindx = baseindx;
% for efficiency do not redraw those that do not change.
sliderchangedd = false(1,numel(info.sIMG));
sliderchangedd(info.tileDims) = sliderchangedd(1:numel(info.tileDims));
updatelist = find(sum(sliderchanged) ~= sum(sliderchangedd(info.subfigdims),2)');
% set(fighndl,'userdata',[]);
% set(fighndl,'userdata',info);
redrawsubplots(fighndl, updatelist)

function scrollweelupdate(src,event,infosrc)
if event.VerticalScrollCount ~= 0 
fighndl = infosrc(1);
info = get(fighndl,'userdata');
mn = get(info.activeslider,'Min');
mx = get(info.activeslider,'Max');
stp = get(info.activeslider,'SliderStep'); % step relative to max-min (ARG!!)
newpos = get(info.activeslider,'value') + event.VerticalScrollCount * stp(1) * (mx-mn);
set(info.activeslider,'value' , max( mn ,min( mx, newpos )));
clear info;
sliderupdate(src,event,infosrc);
end;

function contrastval = getcontrastval(info)
[mn, mx] = getminmax(info);
contrastval = (mx-mn)*2^(-get(info.sliders(numel(info.tileDims)+2),'value'));

function [mn, mx] = getminmax(info)
mn = info.minmax(:,1);
mx = info.minmax(:,2);
dolog = info.drawprop(2);
if numel(mn)>1
    midx = 1;
    switch info.drawmethod % reduce complex data:
        case 'P'
            midx = 2;
            if mx(midx)>4/5*pi && mn(midx)<-4/5*pi
                mn(midx) = -pi;
                mx(midx) = pi;
            end;
        case 'R'
            midx = 3;
        case 'I'
            midx = 4;
    end;
    if dolog
        [mn,mx]=deal([log(mn(1)) -pi log(mn(1)) -pi],[log(mx(1)) pi log(mx(1)) pi]);
        mn = max(mn, mx-30);
    end;
    mn = mn(midx);
    mx = mx(midx);
elseif dolog
    info.drawmethod = 'R';
    if mx<0
        [mn,mx] = deal(log(-mx),log(-mn));
    else
        mn = log(max(eps*abs(mx),mn));
        mx = log(abs(mx));
    end;
    mx = real(mx);
    mn = max(mx-30,real(mn));
end;

function updateminmax(info)
if info.drawprop(1)==2
    return;
end;
[mn, mx] = getminmax(info);
centerslider = numel(info.tileDims)+1;
centermin = get(info.sliders(centerslider),'Min');
centermax = get(info.sliders(centerslider),'Max');
if mn == centermin && mx == centermax
    return;
end
curcenter = get(info.sliders(centerslider),'value');
newcenter = ((curcenter-centermin)/(centermax-centermin))*(mx-mn)+mn;
set(info.sliders(centerslider),'Min',mn,'Max', mx,'Value',newcenter);
sliderpos = get(info.sliders(1),'UserData');
sliderpos(centerslider) = newcenter;
set(info.sliders(1),'UserData',sliderpos);
% update text only:
k = numel(info.tileDims)+1; 
set(info.texts(k), 'String' ,  sprintf(get(info.texts(k),'UserData'),newcenter)); 
k = numel(info.tileDims)+2; 
set(info.texts(k), 'String' ,  sprintf(get(info.texts(k),'UserData'),getcontrastval(info)));
redrawsubplots(info.fighnld, 1:numel(info.subfigs));


function redrawsubplots(fighndl, updatelist)
info = get(fighndl,'userdata');
doscale = info.drawprop(1);
dolog = info.drawprop(2);
[mn, mx] = getminmax(info);
sliderpos = get(info.sliders(1),'UserData');
baseindx = repmat({1},numel(info.sIMG),1);
baseindx(info.tileDims) = mat2cell(sliderpos(1:numel(info.tileDims)),ones(1,numel(info.tileDims)),1);
if doscale~=2
    adj = (mx-mn)*2^(-1-sliderpos(end));
    [mn ,mx] = deal( sliderpos(end-1)-adj,sliderpos(end-1)+adj);
end;
for m=updatelist
    if ~ishandle(info.imgs(m))
        stopimagebrowse(fighndl);
        return;
    end;
    
    curindx = baseindx;%info.baseindx;
    curindx(info.subfigdims(m,:)) = {':'};
    curslice = squeeze(info.IMG(curindx{:}));
    if any(diff(info.subfigdims(m,:))<0)
        [dummy, sdimindx] = sort(info.subfigdims(m,:));
        curslice = ipermute(curslice,sdimindx);
    end;
    if dolog
        curslice = log(double(curslice));
    end;
    if doscale==2
        [mn,mx] = advancedminmax(curslice(:));
    end;
    
    switch info.drawmethod % reduce complex data:
        case 'M'
            curslice = abs(curslice);
        case 'P'
            curslice = angle(curslice);
        case 'R'
            curslice = real(curslice);
        case 'I'
            curslice = imag(curslice);
    end;
    if ~isreal(curslice) 
        curslice = real(curslice); % avoid problems with log of negative real values
    end;

    switch doscale
    case {1,2}
        sc = (256-1e-11)./(mx-mn);
        curslice = uint8(floor((double(curslice)-mn).*sc));
    end;
    set(info.imgs(m),'CData',curslice);
end;

function resizefig(src,event,infosrc)%#ok : = event
info = get(infosrc,'userdata');
figsz = get(info.fighnld,'position');
if ~ishandle(info.panel)
    stopimagebrowse(infosrc);
    return;
end;
set(info.panel,'position',[110/figsz(3) 0 (figsz(3)-110)/figsz(3) 1]);

function imageclick(src,event,infosrc) %#ok: = event
info = get(infosrc(1),'userdata');
if ~isequal(get(info.fighnld,'SelectionType'),'normal')
    return; % only process left clicks.
end;
cursorpos =  get(info.subfigs(infosrc(2)),'CurrentPoint');
cursorpos = cursorpos(1,[2 1]);
curdims = info.subfigdims(infosrc(2),1:2);
cursorpos = round((cursorpos-info.rngIMG(1,curdims))./ ([-1 1]*info.rngIMG(:,curdims)).* (info.sIMG(curdims)-1)+1);
if all(cursorpos>0 & cursorpos<=info.sIMG(curdims))
    set(info.sliders(info.tileDims==curdims(1)),'Value',cursorpos(1,1));
    set(info.sliders(info.tileDims==curdims(2)),'Value',cursorpos(1,2));
end;
sliderupdate(info.fighnld,[],info.fighnld);

function stopimagebrowse(infosrc)
disp('Imagebrowse figure corrupt; stopping imagebrowse. This is usually due to ''external'' plotting or clf.');
info = get(infosrc,'userdata');
set(info.fighnld,'ResizeFcn',[]);
set(info.fighnld,'userdata',[]);
set(info.fighnld,'WindowButtonMotionFcn',[]);
for k=1:numel(info.sliders)
    if ishandle(info.sliders(k))
        set(info.sliders(k),'callback',[]);
    end;
end;
for m=1:numel(info.imgs)
    if ishandle(info.imgs(m))
        set(info.imgs(m),'ButtonDownFcn',[]);
    end;
end;

function selcbk(source,eventdata, infosrc)%#ok
if ~isequal(eventdata.OldValue,eventdata.NewValue)
    info = get(infosrc,'userdata');
    info.drawmethod = get(eventdata.NewValue,'Tag');
    set(infosrc,'userdata',[]);
    set(infosrc,'userdata',info);
    updateminmax(info);
end;

function togglelog(source,eventdata, infosrc)%#ok
% if ~isequal(eventdata.OldValue,eventdata.NewValue)
    info = get(infosrc,'userdata');
    info.drawprop(2) = (get(source,'Value') == get(source,'Max'));
    set(infosrc,'userdata',[]);
    set(infosrc,'userdata',info);
    updateminmax(info);
% end;

function setzoom(source,eventdata, infosrc)%#ok
info = get(infosrc,'userdata');
for m=1:numel(info.subfigs)
    if ishandle(info.subfigs(m))
        ax = info.subfigs(m);
        set(ax,'units','pixels');
        loc = get(ax,'Position');
        set(ax,'units','normalized');
        xl = get(ax,'xlim');
        yl = get(ax,'ylim');
        set(ax,'xlim',mean(xl)+[1 loc(3)]-(1+loc(3))/2,'ylim',mean(yl)+[1 loc(4)]-(1+loc(4))/2);
    end;
end;    

function [bool] = isvalidImageBrowsefigure(curfig)
bool = ~isempty(curfig);
if ~bool, return;end;
%  bool = all(isfield(get(curfig,'UserData'), {'IMG','sIMG','rngIMG'}));  % isfield does not support cell list of strings on old MATLAB, so use replacement code below:
reqfields = {'IMG','sIMG','rngIMG'}; 
ud = get(curfig,'UserData');
for k=1:numel(reqfields)
    bool = bool && isfield( ud, reqfields{k} ); % assume true imagebrowse figure when these fields are present
end;
if ~bool, return;end;
ud = get(curfig,'UserData');
bool  = ishandle(ud.fighnld) && isequal(ud.fighnld,curfig) && ishandle(ud.panel) && all(ishandle([ud.subfigs;ud.imgs]));
%if ~bool, return;end;

